package jwt

import (
	"errors"
	"time"

	"gopkg.in/mgo.v2/bson"

	"gitee.com/dayu1985/framework/net/web"
	"gitee.com/dayu1985/framework/storage/cache"

	jwt_lib "github.com/dgrijalva/jwt-go"
	"github.com/dgrijalva/jwt-go/request"
	"github.com/gin-gonic/gin"
)

const ERRNO_UNAUTHORIZED = 10001

type Token = jwt_lib.Token

type GinJWT struct {
	expiration time.Duration
	secret     string
	jtis       cache.Cacher
}

func NewGinJWT(expiration time.Duration, secret string, cacher cache.Cacher) *GinJWT {

	return &GinJWT{
		expiration: expiration,
		secret:     secret,
		jtis:       cacher,
	}
}

type Signature struct {
	AccessToken  string `json:"access_token"`
	RefreshToken string `json:"refresh_token"`
}

func (this *GinJWT) Sign(ctx map[string]interface{}) (*Signature, error) {

	token := jwt_lib.New(jwt_lib.GetSigningMethod("HS256"))
	exp := time.Now().Add(this.expiration).Unix()

	claims := jwt_lib.MapClaims{
		"exp": exp,
	}

	for k, v := range ctx {
		claims[k] = v
	}

	token.Claims = claims

	access_token, err := token.SignedString([]byte(this.secret))

	if err != nil {
		return nil, err
	}

	jti := bson.NewObjectId().Hex()

	err = this.jtis.Set(jti, exp)
	if err != nil {
		return nil, err
	}

	return &Signature{
		AccessToken:  access_token,
		RefreshToken: jti,
	}, err

}

func (this *GinJWT) Auth(callback func(ctx *gin.Context, token *Token)) gin.HandlerFunc {

	return func(ctx *gin.Context) {

		token, err := request.ParseFromRequest(ctx.Request, request.HeaderExtractor{"Authorization"}, func(token *jwt_lib.Token) (interface{}, error) {
			b := ([]byte(this.secret))
			return b, nil
		})

		if err != nil {
			web.Bad(ctx, ERRNO_UNAUTHORIZED, err.Error())
		} else {
			callback(ctx, token)
		}
	}
}

func (this *GinJWT) Refresh(access_token string, refresh_token string) (*Signature, error) {

	ts := time.Now().Unix()
	var exp int64
	exists, err := this.jtis.Get(refresh_token, &exp)

	this.jtis.Delete(refresh_token)

	if err != nil {
		return nil, err
	}

	if !exists {
		return nil, errors.New("刷新凭据不存在!")
	}

	if exp < ts {
		return nil, errors.New("刷新凭据已过期!")
	}

	token, err := jwt_lib.Parse(access_token, func(token *Token) (interface{}, error) {
		b := ([]byte(this.secret))
		return b, nil
	})

	if !token.Valid {
		return nil, errors.New("授权凭据已过期!")
	}

	claims := token.Claims.(jwt_lib.MapClaims)
	claims["exp"] = time.Now().Add(this.expiration).Unix()
	//claims["exp"] = time.Now().Add(time.Second * time.Duration(this.expiration)).Unix()
	token.Claims = claims

	access_token, err = token.SignedString([]byte(this.secret))

	if err != nil {
		return nil, err
	}

	jti := bson.NewObjectId().Hex()
	this.jtis.Set(jti, exp)

	return &Signature{
		AccessToken:  access_token,
		RefreshToken: jti,
	}, err
}
